#!/usr/bin/perl
#
# osztrapd - daemon of the SNMP trap interface for Zabbix
# Copyright (C) 2010 Open Sistemas <imasd@opensistemas.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

use strict;
use warnings;

use Getopt::Std;
use Sys::Syslog;
use YAML;
use DBI;

use OpenSistemas::OSZTrap;

use vars qw(%opts $app);

my $PROG = "osztrapd";
my $VERSION = '1.0';

my $usagetxt = qq {$PROG ver $VERSION
Copyright (C) Open Sistemas <imasd\@opensistemas.com>

Usage:

 $PROG [OPTIONS] osztrapd.conf

Options:

  -h          This help screen
  -p file     PID file (default: /tmp/$PROG.PID)
  -d          Debug (attached to the console)

Check the man page for further information.
};

sub send_trap
{
  my $error = $app->trap();
  syslog('err', "while sending heartbeat trap: " .$error) if $error;
  alarm $app->timeout();
}

sub terminate
{
  alarm 0;

  $app->end();
  syslog('info', "TERM caught, stopping in ~" .$app->pollInterval() ." seconds");
}

exit 1 if !getopts('hdp:', \%opts);

if ($opts{h})
{
  print $usagetxt;
  exit 0;
}

if(@ARGV ne 1)
{
  print STDERR "ERROR: missing argument, try -h\n" if @ARGV ne 1;
  exit 1;
}

my $pidFile = "/tmp/$PROG.PID";
$pidFile = $opts{p} if $opts{p};

if(-f $pidFile)
{
  print STDERR "ERROR: service already running, check $pidFile\n";
  exit 1;
}

my $conf = YAML::LoadFile($ARGV[0]);

if(!$conf)
{
  printf STDERR "ERROR: loading $ARGV[0]\n";
  exit 1;
}

if(!$conf->{destination} || keys(%{$conf->{destination}}) < 1)
{
  printf STDERR "ERROR: you must provide at least one destination\n";
  exit 1;
}

if(!$conf->{zabbix} || !$conf->{zabbix}->{dsn})
{
  printf STDERR "ERROR: you must provide a valid DSN string for your database\n";
  exit 1;
}

if(!$conf->{zabbix} || !$conf->{zabbix}->{username} || !$conf->{zabbix}->{password})
{
  printf STDERR "ERROR: you must provide username/password to access your database\n";
  exit 1;
}

$app = OpenSistemas::OSZTrap->new();
$app->sysname($conf->{sysname}) if $conf->{sysname};
$app->community($conf->{community}) if $conf->{community};
$app->timeout($conf->{heartbeatTss}) if $conf->{heartbeatTss};
my $pollInterval = $conf->{pollInterval} if $conf->{pollInterval};

foreach (keys(%{$conf->{destination}}))
{
  print STDERR "DEBUG: adding session( $_:$conf->{destination}->{$_} @ " .$app->community() ." )\n" if $opts{d};

  my $error = $app->session($_, $conf->{destination}->{$_});

  if($error)
  {
    printf STDERR "ERROR: creating SNMP session: $error\n";
    exit 1;
  }
}

print STDERR "DEBUG: heartbeatTss is " .$app->timeout() .", DEBUG logging to syslog\n" if $opts{d};

openlog($PROG, 'pid', 'daemon');

if(!$opts{d})
{
  syslog('info', "Starting $PROG v$VERSION, conf is $ARGV[0], pidfile is $pidFile");

  my $pid;

  # daemonize
  local $SIG{'HUP'} = 'IGNORE';
  if ($pid = fork())
  {
    open(PID, ">$pidFile");
    print(PID "$pid");
    close(PID);
    exit 0;
  }

  close(STDIN);
  close(STDOUT),
  close(STDERR);
  open(STDIN,  "+>/dev/null");
  open(STDOUT, "+>&STDIN");
  open(STDERR, "+>&STDIN");
}

$SIG{'TERM'} = 'terminate';
$SIG{'ALRM'} = 'send_trap';
alarm $app->timeout();

print STDERR 'DEBUG: trying to connect dbi:mysql:' .$conf->{zabbix}->{dsn}
  .', user: ' .$conf->{zabbix}->{username} ."\n" if $opts{d};

my $dbh = DBI->connect('dbi:mysql:' .$conf->{zabbix}->{dsn},
  $conf->{zabbix}->{username},
  $conf->{zabbix}->{password},
  {'RaiseError' => 0});

if(!$dbh)
{
  syslog('err', 'Database connection failed, terminating');
  closelog();

  $app->closeSessions();

  unlink($pidFile) if !$opts{d};
  exit 1;
}

$app->connection($dbh);

syslog('info', $app->compatibility());
print STDERR 'DEBUG: ' .$app->compatibility() ."\n" if $opts{d};

while($app->running())
{
  print STDERR "DEBUG: sleep for " .$app->pollInterval() ." secs\n" if $opts{d};
  sleep $app->pollInterval();

  print STDERR "DEBUG: polling...\n" if $opts{d};

  my $error = $app->poll();
  syslog('err', "while sending alarm trap: " .$error) if $error;
}

$app->closeSessions();
$app->disconnect();

unlink($pidFile) if !$opts{d};

syslog('info', "cleanning done, bye");

closelog();

exit 0;

=pod

=head1 NAME

osztrapd - daemon of the SNMP trap interface for Zabbix

=head1 DESCRIPTION

This is the daemon that provides a SNMP trap interface for Zabbix.

B<osztrapd> uses SNMP version 2c.

=head1 USAGE

osztrapd [OPTIONS] osztrapd.conf

=over

=item -h

help screen

=item -p file

PID file (default: /tmp/osztrapd.PID)

=item -d

enable debug (attached to the console)

=back

=head1 CONFIGURATION

osztrapd uses the YAML format for its configuration file:

    -- mandatory, can handle n host/port mappings
    destination:
      <host>: <port>

    -- mandatory, database connection data (MySQL)
    zabbix:
      dsn: database=<database>[;host=<hostname>[;port=<port>]]
      username: <username>
      password: <passowed>

    -- optional, default: hostname
    sysname: <name>

    -- optional, default: public
    community: <community>

    -- optional, default: 60
    heartbeatTss: <time in seconds>

    -- optional, default: 30
    pollInterval: <time in seconds>

Example:

    destination:
      192.168.1.100: 162
    heartbeatTss: 120

    zabbix:
      dsn: database=zabbix
      username: uzabbix
      password: secret

=head2 ZABBIX CONFIGURATION

In order to set up in Zabbix web interface which events must be sent by SNMP trap, you have to create
a new media with the exact name of C<SNMP-TRAP>, C<script> type and C</bin/true> as script.

Then just assign the media to the user enabled to monitor the events to be sent with traps, and set up
the action that will allow filtering to the desired events.

=head3 ZABBIX COMPATIBILITY

B<osztrapd> need to query the Zabbix database for its activity, and the Zabbix database schema has changed
in different versions. The application will try to guess the valid schema, and 1.4.x based
schema and 1.8.x based schema are supported.

=head1 AUTHOR

This script has been written by Juan J. Martinez <jjmartinez@opensistemas.com>.

=head1 LICENSE

This application it's free software and it's published under the GPL3 license.

You should have received a copy of the GNU General Public License along with this program.
If not, see <http://www.gnu.org/licenses/>.

=head1 SEE ALSO

oszsnmpd(1), Net::SNMP(3pm), OSZTRAP-MIB.

Project website: http://snmp4zabbix.opensistemas.com/

=cut

